//Ordonnanceur

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <hw.h>

#define RETURN_SUCCESS 0
#define RETURN_FAILURE 1

#define STACK_SIZE 16384

typedef void (func_t)(void *);

/* ENUM contenant les differents etats dans lequels peuvent se trouver les contextes */
typedef enum {
	ACTIVE,
	ACTIVABLE,
	ATTENTE_SEMAPHORE
} context_state;

/* STRUCT qui represente un contexte */
typedef struct _ctx_s {
	void *  regEsp;
	void *  regEbp;
	func_t * function;
	context_state * state;
	void * args;
	char * buffer;
	struct _ctx_s * previous_context;
	struct _ctx_s * next_context;
} ctx_s;

/* STRUCT qui represente un element de la liste d'attente d'un semaphore */
typedef struct _waiting_context {
	ctx_s * context;
	struct _waiting_context * next_context;
} waiting_context;

/* Struct qui represente un semaphore */
typedef struct {
	int value;
	waiting_context * waiting_list_head;
	waiting_context * waiting_list_tail;
} sem_s;

void f_ping(void * arg);
void f_pong(void * arg);

ctx_s * context_list = NULL;	/* Liste des contextes existants */
ctx_s * current_context = NULL;	/* Contexte actif */

/* Initialise un semaphore */
void sem_init(sem_s * sem, unsigned int val) {
	sem->value = val;
	sem->waiting_list_head = NULL;
	sem->waiting_list_tail = NULL;
}

/* Donne un jeton a un semaphore */
void sem_up(sem_s * sem) {
	sem->value = (sem->value)+1;
	/* Si il y a des contextes en attente, prend le premier contexte et le met au etat activable */
	if(sem->value < 0) {
		waiting_context * temp;
		sem->waiting_list_head->context->state = ACTIVABLE;
		temp = sem->waiting_list_head;
		sem->waiting_list_head = sem->waiting_list_head->next_context;
		free(temp);
		if(sem->value == -1) {
			sem->waiting_list_tail = NULL;
		}
	}
}

/* Prend un jeton d'un semaphore */
void sem_down(sem_s * sem) {
	if(sem->value > 0) {
		sem->value = (sem->value)-1;
	}
	/* Si il n'y a pas de jeton, met le contexte en attente sur le semaphore */
	else {
		waiting_context * wait = malloc(sizeof(waiting_context));
		wait->context = current_context;
		wait->next_context = NULL;
		wait->context->state = ATTENTE_SEMAPHORE;
		if(sem->waiting_list_head == NULL) {
			sem->waiting_list_head = wait;
			sem->waiting_list_tail = wait;
		}
		else {
			sem->waiting_list_tail->next_context = wait;
			sem->waiting_list_tail = wait;
		}
	}
}

/* Cree un contexte */
int create_ctx(int stack_size, func_t f, void *args) {
	irq_disable();
	ctx_s * ctx = malloc(sizeof(ctx_s));
	ctx->regEsp = NULL;
	ctx->regEbp = NULL;
	ctx->function = f;
	ctx->state = ACTIVABLE;
	ctx->args = args;
	ctx->buffer = (char *)malloc(stack_size);
	/* La liste de contextes est initialement vide */
	if(context_list == NULL) {
		context_list = ctx;
		ctx->previous_context = ctx;
		ctx->next_context = ctx;
	}
	/* La liste nest pas vide */
	else {
		ctx->previous_context = context_list->previous_context;
		ctx->previous_context->next_context = ctx;
		ctx->next_context = context_list;
		context_list->previous_context = ctx;
	}
	irq_enable();
	return 0;
}

/* Donne la main au prochain contexte */
void yield() {
	irq_disable();
	/* Sauvegarde le contexte courrant */
	if(current_context != NULL) {
		current_context->state = ACTIVABLE;
		asm("\t mov %%esp,%0" : "=r"(current_context->regEsp));
		asm("\t mov %%ebp,%0" : "=r"(current_context->regEbp));
		current_context = current_context->next_context;
	}
	/* Il n'y a pas de contexte courrant */
	else {
		/* La liste de contextes est vide */
		if(context_list == NULL) {
			printf("No context/n");
			return;
		}
		/* La premier contexte de la liste devient le contexte courrant */
		else {
			current_context = context_list;
		}
	}
	/* Cest la premiere execution de ce contexte - appelle la fonction associee au contexte */
	if(current_context->regEsp == NULL && current_context->regEbp == NULL) {
		asm("\t mov %0,%%esp" : : "r"(current_context->buffer+STACK_SIZE));
		asm("\t mov %0,%%ebp" : : "r"(current_context->buffer+STACK_SIZE));
		current_context->state = ACTIVE;
		irq_enable();
		(*current_context->function)(current_context->args);
	}
	/* Charge le contexte courrant */
	else {
		asm("\t mov %0,%%esp" : : "r"(current_context->regEsp));
		asm("\t mov %0,%%ebp" : : "r"(current_context->regEbp));
		current_context->state = ACTIVE;
		irq_enable();
		return;
	}
}

void start_sched() {
	setup_irq(TIMER_IRQ,yield);
	start_hw();
}

int main(int argc, char * argv[])
{
    create_ctx(STACK_SIZE, f_pong, NULL);
    create_ctx(STACK_SIZE, f_ping, NULL);
    start_sched();
    sleep(10);
    exit(EXIT_SUCCESS);

	return 0; 
}

void f_ping(void * args) {
	while(1) {
		printf("A\n") ;
		printf("B\n") ;
		printf("C\n") ;
		
	}
}

void f_pong(void *args) {
	while(1) {
		printf("1\n") ;
		printf("2\n") ;
	}
}
